/**
********************************************************************************
* @file     lv_port_fs_fatfs.c
* @brief    对文件的简要说明
* @details  对文件的详细说明
* @author   jcai
* @date     2024-07-12 13:52:44
* @copyright Copyright &copy; 2024 wy1107412334@163.com All rights reserved
* @version  v0.0.1
*
* \n Email:  wy1107412334@163.com
* \n Blog:   None
* \n Github: https://github.com/wy1107412334
* \n Gitee:  https://gitee.com/git.git1107412334.com
*
********************************************************************************
* @note
* None.
********************************************************************************
* @attention
* None.
********************************************************************************
@verbatim
@endverbatim
********************************************************************************
* @par 历史记录
* jcai, 2024-07-12 13:52:44
* - 首次编写
*
*<center>Copyright &copy; 2024 wy1107412334@163.com All rights reserved</center>
********************************************************************************
*/

/* 头文件导入区 ------------------------------------------------------------- */

#include <stdio.h>
#include <string.h>
#include <strings.h>
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
#include "ff.h"
#include "signal_link_list.h"
#include "lv_port_fs_fatfs.h"


/** @defgroup LV_PORT_FS_FATFS LV_PORT_FS_FATFS
* @brief lv_port_fs_fatfs modules
* @{
*/



/* 私有宏定义区 ------------------------------------------------------------- */

#ifndef LV_FS_FATFS_LETTER_STR
#define LV_FS_FATFS_LETTER_STR "F:"
#endif




/* 私有数据类型声明区 ------------------------------------------------------- */


typedef struct
{
    SL_NODE node;
    FIL     fp;
} fil_node;
typedef struct
{
    SL_NODE node;
    DIR     dp;
} dir_node;

typedef struct
{
    FATFS   fs;
    SL_LIST filList;
    char    work[FF_MAX_SS];
} fatfs_ins;




/* 私有变量定义区 ----------------------------------------------------------- */





/** @defgroup LV_PORT_FS_FATFS_Exported_Variables lv_port_fs_fatfs exported variables
* @brief 全局变量定义
* @{
*/

PARTITION VolToPart[FF_VOLUMES] = {
    { .pd = 0, .pt = 1 },   /* "0:" */
};


/** defgroup LV_PORT_FS_FATFS_Exported_Variables
* @}
*/





/* 私有函数定义区 ----------------------------------------------------------- */


/*Initialize your Storage device and File system.*/
static void fatfs_init(fatfs_ins * fs)
{
    /*E.g. for FatFS initialize the SD card and FatFS itself*/
    LBA_t tbl[] = {100, 0};
    char path[100];
    uint32_t ldn = 0;

    snprintf(path, sizeof(path), "%u:", VolToPart[ldn].pd);

mount:
    bzero(&fs->fs, sizeof(fs));
    FRESULT retSt = f_mount (&fs->fs, path, 1);
    if (retSt != FR_OK)
    {
        printf("f_mount() retusns %d\n", retSt);
        if (retSt == FR_NO_FILESYSTEM)
        {
mkfs:
            printf("%u 指定的卷上面没有文件系统，开始格式化文件系统\n", ldn);
            retSt = f_mkfs(path, NULL, fs->work, FF_MAX_SS);
            if (retSt != FR_OK)
            {
                printf("f_mkfs() returns %d\n", retSt);
                printf("格式化失败，开始在物理驱动器 %u 上创建磁盘分区\n",
                       VolToPart[ldn].pd);
                if (retSt == FR_MKFS_ABORTED)
                {
                    retSt = f_fdisk(VolToPart[ldn].pd, tbl, fs->work);
                    if (retSt != FR_OK)
                    {
                        printf("f_fdisk() returns %d\n", retSt);
                    }
                    else
                    {
                        goto mkfs;
                    }
                }
            }
            else
            {
                goto mount;
            }
        }
        fprintf(stderr, "FatFs initial failed\n");
        return;
    }

    /*You code here*/

    SLLIST_INIT(&fs->filList);
}

/**
 * Open a file
 * @param drv       pointer to a driver where this function belongs
 * @param path      path to the file beginning with the driver letter (e.g. S:/folder/file.txt)
 * @param mode      read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR
 * @return          a file descriptor or NULL on error
 */
static void * fatfs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
    BYTE _mode;

    void * f = NULL;
    fatfs_ins * fs = drv->user_data;

    if(mode == LV_FS_MODE_WR) {
        /*Open a file for write*/
        _mode = FA_WRITE;
    }
    else if(mode == LV_FS_MODE_RD) {
        /*Open a file for read*/
        _mode = FA_READ;
    }
    else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) {
        /*Open a file for read and write*/
        _mode = FA_READ | FA_WRITE;
    }
    else
    {
        return f;
    }


    fil_node * p = malloc(sizeof(*p));
    if (p == NULL)  return f;
    bzero(p, sizeof(*p));

    FRESULT res = f_open(&p->fp, path, _mode);
    if (res != FR_OK)
    {
        free(p);
        return f;
    }

    f = &p->node;
    sllist_insert_head(&fs->filList, f);

    return f;
}

/**
 * Close an opened file
 * @param drv       pointer to a driver where this function belongs
 * @param file_p    pointer to a file_t variable. (opened with fs_open)
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t fatfs_close(lv_fs_drv_t * drv, void * file_p)
{
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;
    fatfs_ins * fs = drv->user_data;
    fil_node * p = (fil_node *) file_p;

    if (f_close(&p->fp) != FR_OK)
    {
        res = LV_FS_RES_HW_ERR;
    }
    else
    {
        res = LV_FS_RES_OK;
    }

    /*Add your code here*/
    if (sllist_delete(&fs->filList, &p->node) == 0)
    {
        free(file_p);
    }

    return res;
}

/**
 * Read data from an opened file
 * @param drv       pointer to a driver where this function belongs
 * @param file_p    pointer to a file_t variable.
 * @param buf       pointer to a memory block where to store the read data
 * @param btr       number of Bytes To Read
 * @param br        the real number of read bytes (Byte Read)
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t fatfs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br)
{
    (void) drv;
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;
    fil_node * p = (fil_node *) file_p;

    /*Add your code here*/
    FRESULT ret = f_read(&p->fp, buf, btr, br);
    if (ret == FR_OK)
    {
        res = LV_FS_RES_OK;
    }
    else
    {
        res = LV_FS_RES_HW_ERR;
    }

    return res;
}

/**
 * Write into a file
 * @param drv       pointer to a driver where this function belongs
 * @param file_p    pointer to a file_t variable
 * @param buf       pointer to a buffer with the bytes to write
 * @param btw       Bytes To Write
 * @param bw        the number of real written bytes (Bytes Written). NULL if unused.
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t fatfs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw)
{
    (void) drv;
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;
    fil_node * p = (fil_node *) file_p;

    FRESULT ret = f_write(&p->fp, buf, btw, bw);
    if (ret == FR_OK)
    {
        res = LV_FS_RES_OK;
    }
    else
    {
        res = LV_FS_RES_HW_ERR;
    }

    return res;
}

/**
 * Set the read write pointer. Also expand the file size if necessary.
 * @param drv       pointer to a driver where this function belongs
 * @param file_p    pointer to a file_t variable. (opened with fs_open )
 * @param pos       the new position of read write pointer
 * @param whence    tells from where to interpret the `pos`. See @lv_fs_whence_t
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t fatfs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence)
{
    (void) drv;
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;
    fil_node * p = (fil_node *) file_p;
    int32_t curPtr = f_tell(&p->fp);

    switch (whence)
    {
    case LV_FS_SEEK_SET:
        curPtr = pos;
        break;
    case LV_FS_SEEK_CUR:
        curPtr += (int32_t) pos;
        break;
    case LV_FS_SEEK_END:
        curPtr = f_size(&p->fp) - (int32_t) pos;
        break;
    default:
        curPtr = -1;
        break;
    }

    if (curPtr >= 0)
    {
        FRESULT ret = f_lseek(&p->fp, curPtr);
        if (ret == FR_OK)
        {
            res = LV_FS_RES_OK;
        }
        else
        {
            res = LV_FS_RES_HW_ERR;
        }
    }
    else
    {
        res = LV_FS_RES_INV_PARAM;
    }

    return res;
}

/**
 * Give the position of the read write pointer
 * @param drv       pointer to a driver where this function belongs
 * @param file_p    pointer to a file_t variable
 * @param pos_p     pointer to store the result
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t fatfs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
    (void) drv;
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;

    fil_node * p = (fil_node *) file_p;
    *pos_p = f_tell(&p->fp);
    res = LV_FS_RES_OK;

    return res;
}

/**
 * Initialize a 'lv_fs_dir_t' variable for directory reading
 * @param drv       pointer to a driver where this function belongs
 * @param path      path to a directory
 * @return          pointer to the directory read descriptor or NULL on error
 */
static void * fatfs_dir_open(lv_fs_drv_t * drv, const char * path)
{
    void * dir = NULL;
    fatfs_ins * fs = (fatfs_ins *) drv->user_data;

    dir_node * p = malloc(sizeof(*p));
    if (p == NULL)  return dir;

    FRESULT ret = f_opendir(&p->dp, path);
    if (ret != FR_OK)
    {
        free(p);
        return dir;
    }

    dir = &p->node;
    sllist_insert_head(&fs->filList, dir);

    return dir;
}

/**
 * Read the next filename form a directory.
 * The name of the directories will begin with '/'
 * @param drv       pointer to a driver where this function belongs
 * @param rddir_p   pointer to an initialized 'lv_fs_dir_t' variable
 * @param fn        pointer to a buffer to store the filename
 * @param fn_len    length of the buffer to store the filename
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t fatfs_dir_read(lv_fs_drv_t * drv, void * rddir_p, char * fn, uint32_t fn_len)
{
    (void) drv;
    dir_node * p = (dir_node *) rddir_p;
    FILINFO fni;
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;

    FRESULT ret = f_readdir(&p->dp, &fni);
    if (ret == FR_OK)
    {
        if (strlen(fni.fname) < fn_len)
        {
            strcpy(fn, fni.fname);
            res = LV_FS_RES_OK;
        }
        else
        {
            res = LV_FS_RES_INV_PARAM;
        }
    }
    else
    {
        res = LV_FS_RES_HW_ERR;
    }

    return res;
}

/**
 * Close the directory reading
 * @param drv       pointer to a driver where this function belongs
 * @param rddir_p   pointer to an initialized 'lv_fs_dir_t' variable
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t fatfs_dir_close(lv_fs_drv_t * drv, void * rddir_p)
{
    fatfs_ins * fs = (fatfs_ins *) drv->user_data;
    dir_node * p = (dir_node *) rddir_p;
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;

    FRESULT ret = f_closedir(&p->dp);
    if (ret == FR_OK)
    {
        res = LV_FS_RES_OK;
    }
    else
    {
        res = LV_FS_RES_HW_ERR;
    }

    if (sllist_delete(&fs->filList, &p->node) == 0)
    {
        free(rddir_p);
    }

    return res;
}







/** @defgroup LV_PORT_FS_FATFS_Exported_Functions lv_port_fs_fatfs exported functions
* @brief 全局函数定义
* @{
*/



/**
********************************************************************************
* @brief    简要说明
* @details  详细说明
* @param    none
* @param[in]     a: 该参数用于输入
* @param[in,out] b: 该参数用于输入和输出
* @param[out]    c: 该参数用于输出
* @retval  none
* @retval  0 函数成功
* @retval  1 函数失败
* @return  返回0成功；否则失败
* @note    none
*/
void lv_port_fs_fatfs_init(void)
{
    /*----------------------------------------------------
     * Initialize your storage device and File System
     * -------------------------------------------------*/
    fatfs_ins * p = malloc(sizeof(*p));
    if (p == NULL)  return;

    fatfs_init(p);

    /*---------------------------------------------------
     * Register the file system interface in LVGL
     *--------------------------------------------------*/

    static lv_fs_drv_t fs_drv;
    lv_fs_drv_init(&fs_drv);

    /*Set up fields...*/
    fs_drv.letter = LV_FS_FATFS_LETTER_STR[0];
    fs_drv.open_cb = fatfs_open;
    fs_drv.close_cb = fatfs_close;
    fs_drv.read_cb = fatfs_read;
    fs_drv.write_cb = fatfs_write;
    fs_drv.seek_cb = fatfs_seek;
    fs_drv.tell_cb = fatfs_tell;

    fs_drv.dir_close_cb = fatfs_dir_close;
    fs_drv.dir_open_cb = fatfs_dir_open;
    fs_drv.dir_read_cb = fatfs_dir_read;

    fs_drv.user_data = p;

    lv_fs_drv_register(&fs_drv);
}


/** defgroup LV_PORT_FS_FATFS_Exported_Functions
* @}
*/

/** defgroup LV_PORT_FS_FATFS
* @}
*/


/*********************************** END OF FILE ******************************/
